/*
 * XverizexCopyright 2024 Dmitry Naydolinsky right reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <sys/epoll.h>
#include <pthread.h>

#include "http_struc.h"


static uint64_t global_file_number;

static const uint64_t filesize_mb = 10;
static uint64_t filesize_in_bytes = filesize_mb * 1024 * 1024;



static int is_end (uint8_t *b, ssize_t len)
{
	b += len - 2;

	if (*(b + 0) == 0x0d && *(b + 1) == 0x0a) {
		return 1;
	}

	return 0;
}

void htx_debug (uint8_t *data, uint64_t len) {
	int n = 0;
	uint64_t offset = 0;
	for (int i = 0; i < len; i++) {
		if (i == 0) {
			printf ("%8x: ", offset);
		}
		if (i && i % 16 == 0) {
			printf ("|");
			for (int nd = 0; nd < n; nd++) {
				printf ("%c", data[offset + nd] >= ' ' && data[offset + nd] <= '~'? data[offset + nd]: '.');
			}
			printf ("|");
			printf ("\n");
			offset += 16;
			printf ("%8x: ", offset);
			n = 0;
		}
		printf ("%02x ", data[i]);
		n++;
	}
	int last = 16 - n;
	for (int i = 0; i < last; i++) {
		printf ("   ");
	}

	printf ("|");
	for (int nd = 0; nd < n; nd++) {
		printf ("%c", data[offset + nd] >= ' ' && data[offset + nd] <= '~'? data[offset + nd]: '.');
	}
	for (int i = 0; i < last; i++) {
		printf (".");
	}
	printf ("|");
	printf ("\n");
}

static void htx_parse_req (struct htx_http_req *r, uint8_t *data)
{
	uint64_t len = strlen (data);
	uint64_t tmp_len = 0;

	r->cmd = NULL;
	r->url = NULL;
	r->version = NULL;

	if (data[0] == ' ')
		return;

	uint8_t *cmd = data;

	uint8_t *url = strchr (cmd, ' ');
	if (!url)
		return;

	if (url) {
		*url = 0;
		url++;
	}
	while (*url == ' ') url++;
	tmp_len = url - data;
	if (tmp_len >= len)
		return;

	uint8_t *version = strchr (url, ' ');
	if (!version)
		return;
	if (version) {
		*version = 0;
		version++;
	}
	while (*version == ' ') version++;
	tmp_len = version - data;
	if (tmp_len >= len)
		return;

	r->cmd = strdup (cmd);
	r->url = strdup (url);
	r->version = strdup (version);
}

static uint64_t parse_header (struct htx_header_struc *h, uint8_t *d)
{
	h->name = NULL;
	h->val = NULL;

	uint8_t *end = strchr (d, 0x0);

	uint8_t *val = strchr (d, ':');
	if (!val)
		return 0;

	*val = 0;
	val++;
	while (*val == ' ') val++;
	h->name = strdup (d);
	h->val = strdup (val);

	while (*end == 0) end++;

	return end - d;
}

static void free_headers (struct htx_header_struc *h, uint32_t max_headers)
{
	if (h) {
		for (int i = 0; i < max_headers; i++) {
			if (h[i].name)
				free (h[i].name);
			if (h[i].val)
				free (h[i].val);
		}
		free (h);
	}
}

static void free_req (struct htx_http_req *r)
{
	if (r) {
		if (r->cmd)
			free (r->cmd);
		if (r->url)
			free (r->url);
		if (r->version)
			free (r->version);
		free (r);
	}
}

void htx_parse_headers (struct htx_client_struc *cs, uint8_t *dt, uint64_t len)
{
	free_req (cs->req);
	free_headers (cs->headers, 0);
	cs->req = NULL;
	cs->headers = NULL;
	cs->size_headers = 0;
	cs->last_error = 1;
	cs->size_header_length = 0;

	uint8_t *end = strstr (dt, "\r\n\r\n");	
	uint8_t *data = NULL;

	uint64_t lend = 0;
	if (end) {
		lend = end - dt + 4;
		data = malloc (lend);
		memcpy (data, dt, lend);
		memset (&data[lend - 4], 0, 4);
		end = data + lend;
	} else {
		return;
	}

	/* count lines */
	uint8_t *nline = data;
	uint64_t count_line = 0;
	uint8_t *headers_start_line = NULL;
	while (1) {
		uint8_t *l = strstr (nline, "\r\n");
		if (!l) {
			break;
		}
		*(l + 0) = 0;
		*(l + 1) = 0;
		nline = l + 2;
		if (!headers_start_line) {
			headers_start_line = nline;
		}
		count_line++;
	}

	if (count_line <= 1) {
		cs->last_error = 1;
		return;
	}

	int max_headers = count_line - 1;
	struct htx_header_struc *headers = htx_malloc_and_set (sizeof (struct htx_header_struc) * max_headers, 0);
	struct htx_http_req *r = htx_malloc_and_set (sizeof (struct htx_http_req), 0);
	htx_parse_req (r, data);
	if (!r->cmd || !r->url || !r->version) {
		goto error;
	}

	uint8_t *h = headers_start_line;

	uint64_t tmp_len = h - data;
	if (tmp_len >= len)
		goto error;


	uint32_t c = 0;
	for (uint64_t lt = tmp_len; lt < len;) {
		if (c >= max_headers)
			break;
		uint64_t ret = parse_header (&headers[c++], h);
		if (ret == 0)
			goto error;

		h += ret;
		lt += ret;
	}

	cs->size_headers = max_headers;
	cs->last_error = 0;
	cs->size_header_length = lend;
	cs->req = r;
	cs->headers = headers;
	free (data);
	return;
error:

	free (data);

	free_req (r);

	free_headers (headers, max_headers);

	cs->last_error = 1;
}

static uint32_t is_digit_line (char *v)
{
	int len = strlen (v);
	for (int i = 0; i < len; i++) {
		if (v[i] >= '0' && v[i] <= '9')
			continue;
		return 0;
	}
	return 1;
}

static void check_if_content_length (struct htx_client_struc *cs)
{
	static char *content_length = "Content-Length";

	for (int i = 0; i < cs->size_headers; i++) {

		struct htx_header_struc *h = &cs->headers[i];

		if (!strncmp (h->name, content_length, strlen (content_length) + 1)) {
			if (h->val) {
				if (!is_digit_line (h->val)) {
					cs->is_content_length = 0;
					return;
				}

				cs->size_data = atol (h->val);
				cs->is_content_length = 1;
			}
			return;
		}
	}

	cs->is_content_length = 0;
}

static void handle (struct htx_client_struc *cs, uint8_t *buf, ssize_t len)
{
	char filename[4096];

	uint64_t ret = 0;

	int file_index = -1;

	if (len == -1) {
		free_req (cs->req);
		free_headers (cs->headers, cs->size_headers);
		cs->req = NULL;
		cs->headers = NULL;
		if (cs->fp)
			fclose (cs->fp);
		cs->fp = NULL;
		if (cs->buf)
			free (cs->buf);
		cs->buf = NULL;
		cs->is_end = 1;
		cs->last_error = 1;
		return;
	}

	if (!cs->fp) {

		pthread_mutex_lock (&http->filename_mutex);
		file_index = http->cfg->file_number++;
		pthread_mutex_unlock (&http->filename_mutex);

		snprintf (filename, 4096, HTX_PATTERN_GLOBAL_NUM, http->cfg->tmp_dir ? http->cfg->tmp_dir: ".", file_index);

		cs->fp = fopen (filename, "w");
		cs->new_file = 1;
		cs->size_data = 0;
		cs->is_content_length = 0;
		cs->is_end = 0;
		cs->file_index = file_index;
	}


	buf[len] = 0;


	uint8_t *d = buf;

	if (cs->new_file) {
		htx_parse_headers (cs, buf, len);
		if (cs->last_error == 0) {
			check_if_content_length (cs);
			if (!cs->is_content_length) {
				cs->is_end = 1;
			} else {
				cs->is_end = 0;
			}

			d += cs->size_header_length;
			len -= cs->size_header_length;
		}
		cs->new_file = 0;
	}

	fwrite (d, len, 1, cs->fp);

	if (cs->is_content_length) {
		if ((cs->size_data - len) > cs->size_data) {
			fclose (cs->fp);
			cs->fp = NULL;
			free_req (cs->req);
			free_headers (cs->headers, cs->size_headers);
			cs->req = NULL;
			cs->headers = NULL;
			cs->is_end = 1;
			cs->last_error = 1;
			if (cs->buf)
				free (cs->buf);
			cs->buf = NULL;
			return;
		}
		cs->size_data -= len;
	}

	if (cs->is_content_length) {
		if (cs->size_data == 0) {
			fclose (cs->fp);
			cs->fp = NULL;
			cs->is_end = 1;
			return;
		} else {
			cs->is_end = 0;
		}
	}
}

static uint32_t listen_free_get_index (uint32_t *listen_free, int max_available_clients)
{
	for (int i = 0; i < max_available_clients; i++) {
		if (listen_free[i]) {
			listen_free[i] = 0;
			return i;
		}
	}

	return UINT32_MAX;
}


static void close_client (struct htx_pool_client_struc *pool_cs, int indx)
{
	struct htx_client_struc *deletable_cs = &pool_cs->cs[indx];

	int max = pool_cs->max;

	for (int i = 0; i < max; i++) {
		struct htx_client_struc *cs = &pool_cs->cs[i];

		if (cs == deletable_cs) {
			close (cs->fd);

			int left = max - i;
			int all_left_size = left * sizeof (struct htx_client_struc);

			if ((i + 1) == max)
				break;

			uint8_t *bytes = malloc (all_left_size);
			memcpy (bytes, &pool_cs->cs[i + 1], all_left_size);
			memcpy (&pool_cs->cs[i], bytes, all_left_size);
			free (bytes);
			break;
		}
	}

	if (max > 0) {
		max--;
		memset (&pool_cs->cs[max], 0, sizeof (struct htx_client_struc));
		pool_cs->max = max;
	} else {
		memset (&pool_cs->cs[0], 0, sizeof (struct htx_client_struc));
		pool_cs->max = 0;
	}
}


struct htx_http_struc *htx_init_struct (const char *ip, uint16_t port, int max_avail)
{
	int ret;
	int sock = ret = socket (AF_INET, SOCK_STREAM, 0);
	if (ret == -1) {
		return NULL;
	}

	int opt = 1;
	setsockopt (sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof (opt));
	setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof (opt));

	struct sockaddr_in s;
	s.sin_family = AF_INET;
	s.sin_port = htons (port);
	inet_aton (ip, &s.sin_addr);

	ret = bind (sock, (const struct sockaddr *) &s, sizeof (s));
	if (ret == -1) {
		close (sock);
		return NULL;
	}

	ret = listen (sock, max_avail);
	if (ret == -1) {
		close (sock);
		return NULL;
	}

	struct htx_http_struc *http = htx_malloc_and_set (sizeof (struct htx_http_struc), 0);
	http->server_fd = sock;
	http->max_avail = max_avail;

	pthread_mutex_init (&http->filename_mutex, NULL);
	pthread_mutex_init (&http->client_mutex, NULL);
	pthread_mutex_init (&http->free_indx_mutex, NULL);
	pthread_mutex_init (&http->set_files_mutex, NULL);

	http->epollfd = epoll_create1(0);

	return http;
}


int htx_pool_http_add (struct htx_pool_http *ph, struct htx_http_struc *http)
{
	int max = ph->max;
	int indx = max++;
	struct htx_http_struc **ht = realloc (ph->http, sizeof (struct htx_http_struc) * max);
	if (!ht) {
		return 1;
	}

	ht[indx] = http;
	ph->http = ht;
	ph->max = max;

	return 0;
}

static void free_http (struct htx_http_struc *http)
{
	close (http->server_fd);
	free (http);
}

void htx_pool_http_free (struct htx_pool_http *ph)
{
	for (int i = 0; i < ph->max; i++) {
		free_http (ph->http[i]);
	}

	free (ph);
}

static void init_http (struct htx_http_struc *http)
{


	http->listen_free = htx_malloc_and_set (sizeof (uint32_t) * http->max_avail, 0);

	http->pool_cs = htx_malloc_and_set (sizeof (struct htx_pool_client_struc), 0);
	http->pool_cs->cs = htx_malloc_and_set (sizeof (struct htx_client_struc) * http->max_avail, 0);
	for (int i = 0; i < http->max_avail; i++) {
		http->pool_cs->cs[i].block = HTX_BLOCK_POOL_FILENAME;
		http->listen_free[i] = 1;
	}
}

static void free_index_list (struct htx_http_struc *http, uint32_t indx)
{
	http->listen_free[indx] = 1;
}

static void *accept_thread (void *_data)
{
	struct htx_http_struc *http = (struct htx_http_struc *) _data;
	socklen_t sc = sizeof (struct sockaddr_in);

	struct sockaddr_in c;

	while (1) {
		int client = accept (http->server_fd, (struct sockaddr *) &c, &sc);

		pthread_mutex_lock (&http->free_indx_mutex);
		uint32_t indx = listen_free_get_index (http->listen_free, http->max_avail);
		pthread_mutex_unlock (&http->free_indx_mutex);

		struct htx_client_struc *cs = &http->pool_cs->cs[indx];
		memcpy (&cs->c, &c, sc);
		cs->sc = sc;
		cs->fp = NULL;

		cs->fd = client;
		cs->indx = indx;
		cs->buf = malloc (HTX_MSG_BUF_SIZE + 1);

		pthread_mutex_lock (&http->client_mutex);
		struct epoll_event ev;
		ev.events = EPOLLIN | EPOLLRDHUP;
		ev.data.ptr = cs;
		if (epoll_ctl (http->epollfd, EPOLL_CTL_ADD, client, &ev)) {
			perror ("epollctl: listen sock");
			free_index_list (http, indx);
		}
		pthread_mutex_unlock (&http->client_mutex);
	}
}

static void send_ok (struct htx_client_struc *cs)
{
	char *buf = 
		"HTTP/1.1 200 OK\r\n"
		"Connection: close\r\n"
		"\r\n";
	int len = strlen (buf);

	write (cs->fd, buf, len);
}

static void send_err (struct htx_client_struc *cs)
{
	char *buf = 
		"HTTP/1.1 400 Bad Request\r\n"
		"Connection: close\r\n"
		"\r\n";
	int len = strlen (buf);

	write (cs->fd, buf, len);
}

static void free_cs (struct htx_client_struc *cs)
{
	free_req (cs->req);
	free_headers (cs->headers, cs->size_headers);
}

static void *worker_http (void *_data)
{
	struct htx_http_struc *http = (struct htx_http_struc *) _data;

	int nfds = 0;

	pthread_create (&http->thread_accept, NULL, accept_thread, http);

	struct epoll_event events[HTX_MAX_CLIENT_CONNECTION];

	while (1) {

		nfds = epoll_wait (http->epollfd, events, HTX_MAX_CLIENT_CONNECTION, -1);
		if (nfds == -1) {
			perror ("epoll_wait");
			return NULL;
		}

		for (int i = 0; i < nfds; i++) {

			struct htx_client_struc *cs = events[i].data.ptr;

			struct htx_com_file *com_file = NULL;

			if (events[i].events & EPOLLIN) {
				ssize_t len = read (cs->fd, buf, HTX_MSG_BUF_SIZE);
				handle (cs, buf, len);
			}

			if (cs->is_end) {
				if (cs->last_error == 0) {
					send_ok (cs);
				} else {
					send_err (cs);
				}


				char filename[4096];

				snprintf (filename, 4096, HTX_PATTERN_GLOBAL_NUM, http->cfg->tmp_dir ? http->cfg->tmp_dir: ".", cs->file_index);

				if (http->handle_file) {
					http->handle_file (cs, filename);
				}

				close (cs->fd);

				pthread_mutex_lock (&http->client_mutex);
				epoll_ctl (http->epollfd, EPOLL_CTL_DEL, cs->fd, NULL);
				close_client (http->pool_cs, i);
				free_index_list (http, cs->indx);
				pthread_mutex_unlock (&http->client_mutex);

				free_cs (cs);
			}

			if (events[i].events & EPOLLRDHUP) {
				pthread_mutex_lock (&http->client_mutex);
				epoll_ctl (http->epollfd, EPOLL_CTL_DEL, cs->fd, NULL);
				close_client (http->pool_cs, i);
				free_index_list (http, cs->indx);
				pthread_mutex_unlock (&http->client_mutex);
			}
		}
	}
}

void htx_pool_http_make_srv (struct htx_pool_http *ph)
{
	for (int i = 0; i < ph->max; i++) {
		init_http (ph->http[i]);
		pthread_create (&ph->http[i]->thread, NULL, worker_http, ph->http[i]);
	}
}


void htx_http_set_config (struct htx_http_struc *http, struct htx_http_config *cfg)
{
	http->cfg = cfg;
}

void htx_http_set_handle_file_cb (struct htx_http_struc *http, void (*cb) (struct htx_client_struc *cs, const char *filename))
{
	http->handle_file = cb;
}

#if 0
int main (int argc, char **argv) 
{

	struct htx_pool_http *ph = htx_malloc_and_set (sizeof (struct htx_pool_http), 0);

	struct htx_http_struc *http0 = htx_init_struct ("0.0.0.0", 8080, 5);
	struct htx_http_struc *http1 = htx_init_struct ("0.0.0.0", 8090, 5);

	http0->handle_file = handle_file;

	struct htx_http_config *http0_config = htx_alloc_http_config ();
	http0_config->tmp_dir = strdup ("files");
	htx_http_set_config (http0, http0_config);

	struct htx_http_config *http1_config = htx_alloc_http_config ();
	http1_config->tmp_dir = strdup ("tmp");
	htx_http_set_config (http1, http1_config);

	int ret_pool = 0;
	ret_pool += htx_pool_http_add (ph, http0);
	ret_pool += htx_pool_http_add (ph, http1);

	if (ret_pool > 0) {
		/*
		 * TODO: free cfg
		 */
		htx_pool_http_free (ph);
		exit (EXIT_FAILURE);
	}

	htx_pool_http_make_srv (ph);

	htx_pool_http_loop (ph);
}
#endif
